#pragma once

struct hBitStr;
template <bool IsConst>
struct _hBit
{
	using OwnerTy = std::conditional_t<IsConst, const hBitStr, hBitStr>;
	OwnerTy& _owner;
	size_t _id;

	_hBit& operator=(bool b);
	operator bool() const;
};

template <bool IsConst>
struct _hBitIter
{
	using OwnerTy = std::conditional_t<IsConst, const hBitStr, hBitStr>;
	OwnerTy* _pOwner;
	size_t _id;

	auto operator->();
	auto operator*() { return _hBit<IsConst>{*_pOwner, _id}; }
	auto& operator++();
	auto operator++(int);
	bool operator==(const _hBitIter& other) const;
	bool operator!=(const _hBitIter& other) const { return !(*this == other); }
};

using hBit = _hBit<false>;
using hCBit = _hBit<true>;
struct hBitStr
{
	using Iter = _hBitIter<false>;
	using CIter = _hBitIter<true>;

	size_t _beg = 0;
	size_t _end = 0;
	std::deque<size_t> _data;

	size_t size() const { return _end - _beg; }
	hBitStr popFront(size_t n);
	hBit back();
	hCBit back() const;
	auto begin() { return Iter{ this, 0 }; }
	auto begin() const { return CIter{ this, 0 }; }
	auto end() { return Iter{ this, _end - _beg }; }
	auto end() const { return CIter{ this, _end - _beg }; }
	void clear();

	template <size_t N>
	uint64_t getUint() const;
	uint8_t getUint8() const { return (uint8_t)getUint<8>(); }
	uint16_t getUint16() const { return (uint16_t)getUint<16>(); }

	hBit operator[](size_t id) { return hBit{ *this, id }; }
	hCBit operator[](size_t id) const { return hCBit{ *this, id }; }
	hBitStr& operator<<(uint64_t ul);
	hBitStr& operator<<(const hBitStr& other);
	template <size_t N>
	hBitStr& operator<<(const std::bitset<N>& s);
	template <size_t N>
	hBitStr& operator>>(std::bitset<N>& s);
	hBitStr& operator>>(bool& b);
	explicit operator bool() { return _beg != _end; }

	std::ostream& fillOs(std::ostream& os) const;
};

struct hBitStrView
{
	const hBitStr& _str;
	size_t _id = 0;

	template <typename T>
	T& popFront(size_t n, T& t);

	std::ostream& fillOs(std::ostream& os) const;
};

constexpr size_t dtBitSize = std::ratio_multiply<std::ratio<sizeof(std::_Get_first_parameter<decltype(hBitStr::_data)>::type)>, std::ratio<8>>::num;
constexpr size_t dtBitMask = std::ratio_subtract<std::ratio<dtBitSize>, std::ratio<1>>::num;
constexpr size_t dtBitOffset = std::ratio_subtract<std::ratio<std::bit_width(dtBitSize)>, std::ratio<1>>::num;

std::ostream& operator<<(std::ostream& os, const hBitStr& str);
std::ostream& operator<<(std::ostream& os, const hBitStrView& str);
namespace _hBitStrDetail
{
	union Bit8;
	template <bool IsConst>
	struct Bit
	{
		using OwnerTy = std::conditional_t<IsConst, const Bit8, Bit8>;
		OwnerTy& _owner;
		uint8_t _id;

		Bit& operator=(bool b);
		operator bool() const;
	};

	union Bit8
	{
		int8_t _val = 0;
		uint8_t _uval;

		Bit<false> operator[](uint8_t b) { return Bit<false>{ *this, b }; }
		Bit<true> operator[](uint8_t b) const { return Bit<true>{ *this, b }; }
		std::ostream& fillOs(std::ostream& os) const;
	};
}

#include "hBitStrImpl.h"